import sys
if sys.version_info > (3, 0):
	from tkinter import *
	from tkinter.filedialog import askopenfilename,askdirectory
else:
	from Tkinter import *
	from tkFileDialog import askopenfilename,askdirectory
	import tkMessageBox

import threading
import re
import platform

gui = None

class Gui(Tk):
	def __init__(self,title):
		Tk.__init__(self)

		self.wm_title(title)
		self.geometry('800x600')
		self.config(padx=3,pady=3)

		#Drag & Drop stuff
		self.pathstoadd=None
		self.dnd=Dropable(self,self.dropFilesCbk)

		#thread var
		self.thread=None
		self.threadlock=threading.Lock()
		self.threadevent=threading.Event()

		#stop all process
		self.stopall=False

		#UI flag
		self.logshown=False

		#Thread context var
		self.thread_ctxt={}
		self.thread_ctxt['running']=0
		self.thread_ctxt['progress']=-1
		self.thread_ctxt['step']=0
		self.thread_ctxt['nbsteps']=0
		self.thread_ctxt['progressmsg']=""
		self.thread_ctxt['log']=""
		self.thread_ctxt['displaymsg']=""
		self.thread_ctxt['askIDtoUser']=""
		self.thread_ctxt['userID']=""
		self.thread_ctxt['fileparams']={}

	
		#ui
		self.grid_columnconfigure(0,weight=1)
		self.grid_columnconfigure(1,weight=1)
		self.grid_rowconfigure(1,weight=1)
		rowi=0

		Label(self, text="File list queue").grid(row=rowi,columnspan=2,sticky=W+E)
		
		#File list
		frame=Frame(self)
		frame.grid_columnconfigure(0,weight=1)
		frame.grid_rowconfigure(0,weight=1)
		sb1=Scrollbar(frame,orient=VERTICAL)
		sb2=Scrollbar(frame,orient=HORIZONTAL)
		self.filelist = Listbox(frame,yscrollcommand=sb1.set,xscrollcommand=sb2.set)
		sb1['command']=self.filelist.yview
		sb2['command']=self.filelist.xview
		self.filelist.grid(row=0,column=0,sticky=W+E+N+S)
		sb1.grid(row=0,column=1,sticky=N+S)
		sb2.grid(row=1,column=0,sticky=W+E)
		rowi=rowi+1
		frame.grid(row=rowi,columnspan=2,sticky=W+E+N+S)

		#Encode param
		rowi=rowi+1
		Button(self,text="Encoding Preferences",command=self.openPrefs).grid(row=rowi,column=0,sticky=W+E)
		self.showprefs=IntVar()
		Checkbutton(self,text="Review encoding param for each files",variable=self.showprefs).grid(row=rowi,column=1,sticky=W+E)
	
		#Buttons
		rowi=rowi+1
		Button(self,text="Add",command=self.addfile).grid(row=rowi,column=0,sticky=W+E)
		Button(self,text="Remove",command=self.removefile).grid(row=rowi,column=1,sticky=W+E)
		rowi=rowi+1
		self.logbutton=Button(self,text="Show Log",command=self.showlog)
		self.logbutton.grid(row=rowi,column=0,sticky=W+E)
		Button(self,text="Quit",command=self.quit).grid(row=rowi,column=1,sticky=W+E)
	
		#status bar
		rowi=rowi+1
		self.statustext=StringVar()
		Label(self, textvariable=self.statustext, bd=1, relief=SUNKEN).grid(row=rowi,columnspan=2,sticky=W+E)

		#Log window
		self.logwin=Toplevel(self,padx=3,pady=3)
		self.logwin.protocol("WM_DELETE_WINDOW","pass")
		self.logwin.withdraw()
		logframe=Frame(self.logwin)
		logframe.grid_columnconfigure(0,weight=1)
		logframe.grid_rowconfigure(0,weight=1)
		sb1=Scrollbar(logframe,orient=VERTICAL)
		sb2=Scrollbar(logframe,orient=HORIZONTAL)
		self.logw=Text(logframe,yscrollcommand=sb1.set,xscrollcommand=sb2.set,wrap=NONE)
		sb1['command']=self.logw.yview
		sb2['command']=self.logw.xview
		self.logw.grid(row=0,column=0,sticky=W+E+N+S)
		sb1.grid(row=0,column=1,sticky=N+S)
		sb2.grid(row=1,column=0,sticky=W+E)
		logframe.pack(fill=BOTH,expand=1)
	
		#Progress window
		self.progresswindow=Toplevel(self,padx=10,pady=10)
		self.progresswindow.protocol("WM_DELETE_WINDOW","pass")
		self.progresswindow.wm_attributes("-topmost", 1)
		self.progresswindow.withdraw()
		Label(self.progresswindow,text="Progress...").pack()
		self.progressmsgfile=StringVar()
		Label(self.progresswindow,textvariable=self.progressmsgfile).pack()
		self.progressfile=Meter(self.progresswindow,relief='ridge',bd=3)
		self.progressfile.pack()
		self.progressmsg=StringVar()
		Label(self.progresswindow,textvariable=self.progressmsg).pack()
		self.progress=Meter(self.progresswindow,relief='ridge',bd=3)
		self.progress.pack()
		Button(self.progresswindow,text="Cancel",command=lambda:self.stopProcessing()).pack()

	def browsefilename(self,parent,var):
		var.set(str(askopenfilename(title="Open file to transcode",parent=parent,filetypes=[("Video files",("*avi","*.mkv","*.mp4")),("All files","*.*")],initialfile=var.get())))

	def browsedir(self,parent,var):
		var.set(str(askdirectory(title="Select directory",parent=parent,mustexist=True,initialdir=var.get())))
	def showlog(self):
		if self.logshown:
			self.logwin.withdraw()
			self.logbutton['relief']=RAISED
		else:
			self.logwin.deiconify()
			try:
				m = re.match("(\d+)x(\d+)[-+](\d+)[-+](\d+)",self.geometry())
				ng='%sx%d+%s+%d' % (m.group(1), int(m.group(2))/2, m.group(3), self.winfo_rooty()+self.winfo_height()+3)
				self.logwin.geometry(ng)
			except:
				pass
			self.logbutton['relief']=SUNKEN
		self.logshown=not self.logshown
	
	def updatestatus(self,str):
		self.statustext.set(str)

	def mainloop(self):
		self.processfile()
		self.after(10,self.updateui)
		self.after(10,self.dropFilesWatch)
		Tk.mainloop(self)

	def askIDtoUser(self,title,msg):
		diag=Toplevel(self)
		diag.config(width=400)
		diag.title(msg)
		Label(diag,text=msg).pack(fill=X,expand=1)
		rep=StringVar()
		Entry(diag, textvariable=rep).pack(fill=X,expand=1)
		Button(diag,text="Ok",command=lambda: diag.destroy()).pack()

		self.wait_window(diag)
		
		return rep.get()
		
	def dropFilesCbk(self,paths):
		self.pathstoadd=paths

	def dropFilesWatch(self):
		if self.pathstoadd != None:
			paths=self.pathstoadd
			self.pathstoadd=None
			for path in paths:
				log("call add file "+path)
				self.addfile(path)
		self.after(10,self.dropFilesWatch)

	def openPrefs(self):
		guiparam=self.addfilegui(prefonly=True)
		if guiparam['returnval'].get():
			GP=self.getglobalparamcbk()
			GP['DEVICECONFS']=guiparam['device'].get()
			if guiparam['destchoice'].get() == 1:
				GP['DESTDIR']=""
				GP['FINALDIR']=""
			elif guiparam['destchoice'].get() == 2:
				GP['DESTDIR']=guiparam['dest'].get()
				GP['FINALDIR']=""
			else:
				GP['DESTDIR']=guiparam['dest'].get()
				GP['FINALDIR']=guiparam['destfinal'].get()
			self.getglobalparamcbk(GP)

	def addfile(self,path=None):
		if path == None or self.showprefs.get() == 1:
			guiparam=self.addfilegui(path=path)
			Ok=guiparam['returnval'].get()
			path=guiparam['path'].get()
			deviceconfs=guiparam['device'].get()
			dest=guiparam['dest'].get()
			destfinal=guiparam['destfinal'].get()

		else:
			Ok=True
			GP=self.getglobalparamcbk()
			deviceconfs=GP['DEVICECONFS']
			dest=GP['DESTDIR']
			destfinal=GP['FINALDIR']

		if Ok:
			filename=path
			if filename != "":
				self.filelist.insert(END, filename)
				with self.threadlock:
					self.thread_ctxt['fileparams'][filename]={}
					self.thread_ctxt['fileparams'][filename]['deviceconfs']=deviceconfs
					self.thread_ctxt['fileparams'][filename]['dest']=dest
					self.thread_ctxt['fileparams'][filename]['destfinal']=destfinal
					self.thread_ctxt['nbsteps']=self.thread_ctxt['nbsteps']+1
				self.processfile()		
	
	def addfilegui(self,path=None,prefonly=False):
		guiparam={}	
		guiparam['path']=StringVar()
		guiparam['device']=IntVar()
		guiparam['destchoice']=IntVar()
		guiparam['dest']=StringVar()
		guiparam['destfinal']=StringVar()
		guiparam['returnval']=BooleanVar(False)

		GP=self.getglobalparamcbk()

		if path != None:
			guiparam['path'].set(path)

		guiparam['destchoice'].set(1)
		if GP['DESTDIR'] != "":
			guiparam['dest'].set(GP['DESTDIR'])
			if GP['FINALDIR'] != "":
				guiparam['destfinal'].set(GP['FINALDIR'])
				guiparam['destchoice'].set(3)
			else:
				guiparam['destchoice'].set(2)
		guiparam['device'].set(GP['DEVICECONFS'])
		
		filep=Toplevel(self,padx=10,pady=10)
		filep.protocol("WM_DELETE_WINDOW","pass")

		if not prefonly:
			f=Frame(filep,border=1,relief=SUNKEN,padx=2,pady=2)
			f.pack(anchor=W,fill=BOTH)
			f.grid_columnconfigure(0,weight=1)

			Label(f,text="Select the file to transcode:").grid(row=0,column=0)
			Entry(f, textvariable=guiparam['path']).grid(row=1,column=0,sticky=W+E)
			Button(f, text="Browse", command=lambda:self.browsefilename(filep,guiparam['path'])).grid(row=1,column=1,sticky=W+E)

		Label(filep,text="Encoding paramaters").pack(anchor=W)
		f=Frame(filep,border=1,relief=SUNKEN,padx=2,pady=2)
		f.pack(anchor=W,fill=BOTH)
		Label(f,text="Select the device to transcode to:").pack(anchor=W)
		Radiobutton(f, text="HD (iPad/iPhone4)", variable=guiparam['device'], value=1).pack(anchor=W)
		Radiobutton(f, text="SD (iPodTouch/iPhone3G)", variable=guiparam['device'], value=2).pack(anchor=W)
		Radiobutton(f, text="HD&SD (universal, 2 encodings)", variable=guiparam['device'], value=3).pack(anchor=W)
		f=Frame(filep,border=1,relief=SUNKEN,padx=2,pady=2)
		f.pack(anchor=W,fill=BOTH)
		f.grid_columnconfigure(1,weight=1)
		f.grid_columnconfigure(3,weight=1)

		Label(f,text="Select output dir (Please read the manual):").grid(row=0,column=0)
		Radiobutton(f, text="Same dir as source", variable=guiparam['destchoice'], value=1).grid(row=1,column=0,sticky=W)
		Radiobutton(f, text="To this dir", variable=guiparam['destchoice'], value=2).grid(row=2,column=0,sticky=W)
		Radiobutton(f, text="First to one dir and then to another", variable=guiparam['destchoice'], value=3).grid(row=3,column=0,sticky=W)
		Entry(f, textvariable=guiparam['dest']).grid(row=2,column=1,sticky=W+E)
		Entry(f, textvariable=guiparam['dest']).grid(row=3,column=1,sticky=W+E)
		Entry(f, textvariable=guiparam['destfinal']).grid(row=3,column=3,sticky=W+E)
		Button(f, text="Browse", command=lambda:self.browsedir(filep,guiparam['dest'])).grid(row=2,column=2,sticky=W+E)
		Button(f, text="Browse", command=lambda:self.browsedir(filep,guiparam['dest'])).grid(row=3,column=2,sticky=W+E)
		Button(f, text="Browse", command=lambda:self.browsedir(filep,guiparam['destfinal'])).grid(row=3,column=4,sticky=W+E)	

		f=Frame(filep,padx=2,pady=2)
		f.pack()
		Button(f,text="Go",command=lambda:guiparam['returnval'].set(True),width=20).grid(row=0,column=0,sticky=W)
		Button(f,text="Cancel",command=lambda:guiparam['returnval'].set(False),width=20).grid(row=0,column=1,sticky=E)
		
		self.wait_variable(guiparam['returnval'])
		filep.destroy()

		return guiparam


	def removefile(self):
		self.filelist.delete(ANCHOR)
	
	def stopProcessing(self):
		self.stopall=True

	def quit(self):
		if self.thread_ctxt['running'] == 1:
			self.stopProcessing()
			self.after(30,self.quit)
			return

		self.cleanquitcbk()
		self.destroy()
		sys.exit(0)
	
	def processfile(self):
		if self.filelist.size() == 0:
			return

		if self.thread is not None:
			if self.thread.isAlive():
				print("Thread already started but not finished, do nothing")
				return
			else:
				print("Thread already started but finished, restart it")

		self.updatestatus("Processing files")
		#open progress window
		if self.progresswindow.state() == 'withdrawn':
			self.progresswindow.deiconify()
			ng='+%d+%d' % (self.winfo_rootx()+self.winfo_width()/4,self.winfo_rooty()+self.winfo_height()/4)
			self.progresswindow.geometry(ng)

		print("Create thread")
		with self.threadlock:
			#Fill needed info
			self.thread_ctxt['file']=self.filelist.get(0)
			self.thread = threading.Thread(target=self.worker)
		self.thread.start()

	def worker(self):
		#try:
		with self.threadlock:
			file=self.thread_ctxt['file']
			if file in self.thread_ctxt['fileparams'].keys():
				fp=self.thread_ctxt['fileparams'][file]
				del self.thread_ctxt['fileparams'][file]
			else:
				fp={}
			self.thread_ctxt['running']=1
		print("process "+file)
		status=self.processfilecbk(file,fp)
		with self.threadlock:
			self.thread_ctxt['running']=2
			self.stopall=False
		#except:
		#	self.updatestatus("Error")
		#	self.thread_ctxt['status']=1

	def updateui(self): 
		needprocessfile=False
	
		with self.threadlock:
			if self.filelist.size() > self.thread_ctxt['nbsteps']:
				self.thread_ctxt['nbsteps']=self.filelist.size()

			if self.thread_ctxt['nbsteps'] != 0:
				if self.thread_ctxt['step'] == self.thread_ctxt['nbsteps']:
					self.progresswindow.withdraw()
					self.thread_ctxt['nbsteps']=0
					self.thread_ctxt['step']=0
					self.updatestatus("Done")
				else:
					self.progressfile.set(float(self.thread_ctxt['step'])/float(self.thread_ctxt['nbsteps']),"File "+str(self.thread_ctxt['step'])+"/"+str(self.thread_ctxt['nbsteps']))
	
			if self.thread_ctxt['progress'] != -1:
				self.progress.set(float(self.thread_ctxt['progress'])/100.0)

			if self.thread_ctxt['progressmsg'] != "":
				self.progressmsg.set(self.thread_ctxt['progressmsg'])
	
			if self.thread_ctxt['log'] != "":
				self.logw['state']=NORMAL
				#if not newline:
				#	self.logw.delete(END+"-2l",END+"-1l")
				self.logw.insert(END,self.thread_ctxt['log'])
				self.logw.see(END)
				self.logw['state']=DISABLED
				self.thread_ctxt['log']=""

			if self.thread_ctxt['displaymsg'] != "":
				tkMessageBox.showerror("Error",self.thread_ctxt['displaymsg'])
				self.thread_ctxt['displaymsg']=""
				self.threadevent.set()

			if self.thread_ctxt['askIDtoUser']!="":
				self.thread_ctxt['userID']=self.askIDtoUser("Please give the movie/serie ID","Please give movie/serie ID for "+self.thread_ctxt['askIDtoUser']+"\n(On www.themoviedbi.org for movie and www.thetvdb.com for series)") 
				self.thread_ctxt['askIDtoUser']=""
				self.threadevent.set()

			if self.thread_ctxt['running']==2:
				#process done for this file
				self.filelist.delete(0)
				self.thread_ctxt['step']=self.thread_ctxt['step']+1
				self.thread_ctxt['running']=0
				needprocessfile=True
						
		if needprocessfile:
			self.processfile()
		self.after(10,self.updateui)

def launchGUI(title,files,processfilecbk,cleanquitcbk,getglobalparamcbk):
	global gui

	gui = Gui(title)
	
	for i in files:
		gui.filelist.insert(END,i)
	
	gui.processfilecbk=processfilecbk
	gui.cleanquitcbk=cleanquitcbk
	gui.getglobalparamcbk=getglobalparamcbk

	GP=getglobalparamcbk()
	if GP['SHOWPREFS']:
		gui.showprefs.set(1)
	else:
		gui.showprefs.set(0)

	gui.mainloop()	

def log(logstr, displaymsg=False):
	logstr=str(logstr)
	print(logstr)

	if gui is not None:
		with gui.threadlock:
			gui.thread_ctxt['log']=gui.thread_ctxt['log']+logstr+"\n"
			if displaymsg:
				gui.thread_ctxt['displaymsg']=logstr
				gui.threadevent.clear()
		if displaymsg:
			gui.threadevent.wait()
		

def progress(progress=-1,msg=""):
	log( "Progress "+str(progress)+"                                                          ",False)

	if gui is not None:
		with gui.threadlock:
			if progress != -1:
				gui.thread_ctxt['progress']=progress
			if msg != "":
				gui.thread_ctxt['progressmsg']=msg

def needToStop():
	if gui is not None:
		return gui.stopall
	else:
		return False

def askIDtoUser(filename):
	if gui is None:
		log("Please give the ID for "+filename)
		return sys.stdin.readline()
	else:
		with gui.threadlock: 
			gui.thread_ctxt['askIDtoUser']=filename
			gui.threadevent.clear()
		gui.threadevent.wait()

		with gui.threadlock:
			ID=gui.thread_ctxt['userID']
			gui.thread_ctxt['userID']=""
		return ID


if platform.system() == "Windows":
	import ctypes
	
	WM_DROPFILES = 563
	GWL_WNDPROC = -4

	class Dropable():
	   def __init__(self,widget,command):
	      self.widget=widget
	      self.command=command
	      log("Installing drag & drop callbacks")

	      #intercept the windows event func
	      SetWindowLong = ctypes.windll.user32.SetWindowLongPtrA
	      hWnd = self.widget.winfo_id()
	      WNDPROC = ctypes.WINFUNCTYPE(ctypes.c_long, ctypes.c_int, ctypes.c_uint, ctypes.c_int, ctypes.c_int)
	      self.newWndProc = WNDPROC(self.myWndProc)
	      self.oldWndProc = SetWindowLong(hWnd, GWL_WNDPROC, self.newWndProc)
	      #allow drag
	      ctypes.windll.shell32.DragAcceptFiles(hWnd, 1)

	   def myWndProc(self, hWnd, msg, wParam, lParam):
	      if msg == WM_DROPFILES:
	         self.dropAction(wParam)
	      CallWindowProc = ctypes.windll.user32.CallWindowProcA
	      status=0
	      try:
        	 status=CallWindowProc(self.oldWndProc, hWnd, msg, wParam,lParam)
	      except:
        	    pass         
	      return status

	   def dropAction(self,hDrop):
	      DragQueryFile = ctypes.windll.shell32.DragQueryFileA
	      n = DragQueryFile(hDrop, -1, None, 0)
	      paths = []
	      for i in range(n):
	         szFile = ctypes.c_buffer(260)
	         DragQueryFile(hDrop, i, szFile, ctypes.sizeof(szFile))
	         paths.append(str(szFile.value))
	      ctypes.windll.shell32.DragFinish(hDrop)
	      log("Receiving from drag & drop "+str(paths))
	      self.command(paths)
else:
	class Dropable():
	  def __init__(self,widget,command):
		  pass


# code from Michael Lange <klappnase (at) freakmail (dot) de>
class Meter(Frame):
	def __init__(self, master, width=300, height=20, bg='white', fillcolor='orchid1',\
			value=0.0, text=None, font=None, textcolor='black', *args, **kw):
		Frame.__init__(self, master, bg=bg, width=width, height=height, *args, **kw)
		self._value = value

		self._canv = Canvas(self, bg=self['bg'], width=self['width'], height=self['height'],\
				highlightthickness=0, relief='flat', bd=0)
		self._canv.pack(fill='both', expand=1)
		self._rect = self._canv.create_rectangle(0, 0, 0, self._canv.winfo_reqheight(), fill=fillcolor,\
				width=0)
		self._text = self._canv.create_text(self._canv.winfo_reqwidth()/2, self._canv.winfo_reqheight()/2,\
				text='', fill=textcolor)
		if font:
			self._canv.itemconfigure(self._text, font=font)

		self.set(value, text)
		self.bind('<Configure>', self._update_coords)

	def _update_coords(self, event):
		'''Updates the position of the text and rectangle inside the canvas when the size of
		   the widget gets changed.'''
		# looks like we have to call update_idletasks() twice to make sure
		# to get the results we expect
		self._canv.update_idletasks()
		self._canv.coords(self._text, self._canv.winfo_width()/2, self._canv.winfo_height()/2)
		self._canv.coords(self._rect, 0, 0, self._canv.winfo_width()*self._value, self._canv.winfo_height())
		self._canv.update_idletasks()

	def get(self):
		return self._value, self._canv.itemcget(self._text, 'text')

	def set(self, value=0.0, text=None):
		#make the value failsafe:
		if value < 0.0:
			value = 0.0
		elif value > 1.0:
			value = 1.0
		self._value = value
		if text == None:
		#if no text is specified use the default percentage string:
			text = str(int(round(100 * value))) + ' %'
		self._canv.coords(self._rect, 0, 0, self._canv.winfo_width()*value, self._canv.winfo_height())
		self._canv.itemconfigure(self._text, text=text)
		self.lift()
		self._canv.update_idletasks()
